
/*
  Find the location of an EXIF attribute in an EXIF profile. The EXIF attribute
  to be found is specified as its numeric tag value (see tag_table). Returns
  a pointer to the attribute in the EXIF profile or NULL if missing or there
  is an error parsing the profile. Returns the EXIF profile byte order if the
  morderp parameter is not NULL.
*/

static unsigned char *
FindEXIFAttribute(const unsigned char *profile_info,
                  const size_t profile_length,
                  const unsigned short tag, int *morderp)
{
  char
    tag_description[MaxTextExtent];

  int
    id,
    level,
    morder;

  size_t
    length;

  unsigned long
    offset;

  unsigned char
    *tiffp,
    *tiffp_max,
    *ifdstack[DE_STACK_SIZE],
    *ifdp,
    *info,
    *attribp;

  unsigned int
    de,
    destack[DE_STACK_SIZE],
    nde;

  MagickBool
    gpsfoundstack[DE_STACK_SIZE],
    gpsfound;

  MagickBool
    debug=MagickFalse;

  attribp = (unsigned char *)NULL;

  assert((ArraySize(format_bytes)-1) == EXIF_NUM_FORMATS);

  {
    const char *
      env_value;

    /*
      Allow enabling debug of EXIF tags
    */
    if ((env_value=getenv("MAGICK_DEBUG_EXIF")))
      {
        if (LocaleCompare(env_value,"TRUE") == 0)
          debug=MagickTrue;
      }
  }
  gpsfound=MagickFalse;
  length=profile_length;
  info=(unsigned char *) profile_info;
  while (length != 0)
    {
      if (ReadByte(&info,&length) != 0x45)
    continue;
      if (ReadByte(&info,&length) != 0x78)
    continue;
      if (ReadByte(&info,&length) != 0x69)
    continue;
      if (ReadByte(&info,&length) != 0x66)
    continue;
      if (ReadByte(&info,&length) != 0x00)
    continue;
      if (ReadByte(&info,&length) != 0x00)
    continue;
      break;
    }
  if (length < 16)
    goto find_attribute_failure;
  tiffp=info;
  tiffp_max=tiffp+length;
  id=Read16u(0,tiffp);
  morder=0;
  if (id == 0x4949) /* LSB */
    morder=0;
  else
    if (id == 0x4D4D) /* MSB */
      morder=1;
    else
      goto find_attribute_failure;
  if (morderp)
    *morderp = morder;
  if (Read16u(morder,tiffp+2) != 0x002a)
    goto find_attribute_failure;
  /*
    This is the offset to the first IFD.
  */
  offset=Read32u(morder,tiffp+4);
  if (offset >= length)
    goto find_attribute_failure;
  /*
    Set the pointer to the first IFD and follow it were it leads.
  */
  ifdp=tiffp+offset;
  level=0;
  de=0U;
  do
    {
      /*
        If there is anything on the stack then pop it off.
      */
      if (level > 0)
        {
          level--;
          ifdp=ifdstack[level];
          de=destack[level];
          gpsfound=gpsfoundstack[level];
        }
      /*
        Determine how many entries there are in the current IFD.
        Limit the number of entries parsed to MAX_TAGS_PER_IFD.
      */
      if ((ifdp < tiffp) || (ifdp+2 > tiffp_max))
        goto find_attribute_failure;
      nde=Read16u(morder,ifdp);
      if (nde > MAX_TAGS_PER_IFD)
        nde=MAX_TAGS_PER_IFD;
      for (; de < nde; de++)
        {
          size_t
            n;

          unsigned int
            c,
            f,
            t;

          unsigned char
            *pde,
            *pval;


          pde=(unsigned char *) (ifdp+2+(12*(size_t) de));
          if (pde + 12 > tiffp + length)
            {
              if (debug)
                fprintf(stderr, "EXIF: Invalid Exif, entry is beyond metadata limit.\n");
              goto find_attribute_failure;
            }
          t=Read16u(morder,pde); /* get tag value */
          f=Read16u(morder,pde+2); /* get the format */
          if ((size_t) f >= ArraySize(format_bytes))
            break;
          c=Read32u(morder,pde+4); /* get number of components */
          n=MagickArraySize(c,format_bytes[f]);
          if ((n == 0) && (c != 0) && (format_bytes[f] != 0))
            {
              if (debug)
                fprintf(stderr, "EXIF: Invalid Exif, too many components (%u).\n",c);
              goto find_attribute_failure;
            }
          if (n <= 4)
            pval=(unsigned char *) pde+8;
          else
            {
              unsigned long
              oval;

              /*
                The directory entry contains an offset.
              */
              oval=Read32u(morder,pde+8);
              if ((oval+n) > length)
                continue;
              pval=(unsigned char *)(tiffp+oval);
            }

          if (debug)
            {
              fprintf(stderr,
                  "EXIF: TagVal=%d  TagDescr=\"%s\" Format=%d  "
                  "FormatDescr=\"%s\"  Components=%u\n",t,
                  EXIFTagToDescription(t,tag_description),f,
                  EXIFFormatToDescription(f),c);
            }

          if (gpsfound)
            {
              if ((t < GPS_TAG_START) || (t > GPS_TAG_STOP))
                {
                  if (debug)
                    fprintf(stderr,
                        "EXIF: Skipping bogus GPS IFD tag %d ...\n",t);
                  continue;
                }
            }
          else
            {
              if ((t < EXIF_TAG_START) || ( t > EXIF_TAG_STOP))
                {
                  if (debug)
                    fprintf(stderr,
                        "EXIF: Skipping bogus EXIF IFD tag %d ...\n",t);
                  continue;
                }
            }

          /*
            Return values for all the tags, or for a specific requested tag.

            Tags from the GPS sub-IFD are in a bit of a chicken and
            egg situation in that the tag for the GPS sub-IFD will not
            be seen unless we pass that tag through so it can be
            processed.  So we pass the GPS_OFFSET tag through, but if
            it was not requested, then we don't return a string value
            for it.
          */
          if (tag == t)
            {
              attribp = pde;
              break;
            }

          if (t == GPS_OFFSET)
            {
              offset=Read32u(morder,pval);
              if ((offset < length) && (level < (DE_STACK_SIZE-2)))
                {
                  /*
                    Push our current directory state onto the stack.
                  */
                  ifdstack[level]=ifdp;
                  de++; /* bump to the next entry */
                  destack[level]=de;
                  gpsfoundstack[level]=gpsfound;
                  level++;
                  /*
                    Push new state onto of stack to cause a jump.
                  */
                  ifdstack[level]=tiffp+offset;
                  destack[level]=0;
                  gpsfoundstack[level]=MagickTrue;
                  level++;
                }
              break; /* break out of the for loop */
            }

          if ((t == TAG_EXIF_OFFSET) || (t == TAG_INTEROP_OFFSET))
            {
              offset=Read32u(morder,pval);
              if ((offset < length) && (level < (DE_STACK_SIZE-2)))
                {
                  /*
                    Push our current directory state onto the stack.
                  */
                  ifdstack[level]=ifdp;
                  de++; /* bump to the next entry */
                  destack[level]=de;
                  gpsfoundstack[level]=gpsfound;
                  level++;
                  /*
                    Push new state onto of stack to cause a jump.
                  */
                  ifdstack[level]=tiffp+offset;
                  destack[level]=0;
                  gpsfoundstack[level]=MagickFalse;
                  level++;
                }
              break; /* break out of the for loop */
            }
        }
    } while (!attribp && (level > 0));
  return attribp;
 find_attribute_failure:
  return (unsigned char *)NULL;
}

